﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Framework.Core.Xml;
using Xunit;

namespace Framework.Tests.Core.Xml
{
    // ReSharper disable UnusedMember.Global
    public class XmlComparerTest
        // ReSharper restore UnusedMember.Global
    {
        private readonly IXmlComparer target;

        public XmlComparerTest()
        {
            target = new XmlComparer();
        }

        private IEnumerable<ComparisonResult> Compare(string expected, string actual)
        {
            return target.Compare(XDocument.Parse(expected), XDocument.Parse(actual));
        }

        // ReSharper disable InconsistentNaming
        // ReSharper disable UnusedMember.Global
        [Fact]
        public void Should_throw_argument_null_exception()
        {
            var xml = XDocument.Parse("<root></root>");

            Assert.Throws<ArgumentNullException>(() => target.Compare(null, null));
            Assert.Throws<ArgumentNullException>(() => target.Compare(xml, null));
            Assert.Throws<ArgumentNullException>(() => target.Compare(null, xml));
            Assert.Throws<ArgumentNullException>(() => target.Compare(new XDocument(), xml));
            Assert.Throws<ArgumentNullException>(() => target.Compare(xml, new XDocument()));
        }

        [Fact]
        public void Xmls_should_be_equals()
        {
            TwoWayCompare(true, "<root></root>", "<root></root>");
        }

        [Fact]
        public void Xmls_should_be_normalizes_to_avoid_closure_comment_differences()
        {
            TwoWayCompare(true, "<root></root>", "<root/>");
        }

        [Fact]
        public void Xmls_with_different_element_should_be_different()
        {
            var expected = XDocument.Parse("<root></root>");
            var actual = XDocument.Parse("<toto></toto>");
            var comparisonResults = target.Compare(expected, actual);

            Assert.NotNull(comparisonResults);
            // ReSharper disable PossibleMultipleEnumeration
            Assert.Equal(1, comparisonResults.Count());
            Assert.Equal("toto", comparisonResults.First().Name);
            // ReSharper disable PossibleNullReferenceException
            Assert.Null(comparisonResults.First().Expected);
            Assert.Equal(actual.Root.ToString(), comparisonResults.First().Actual.ToString());
            // ReSharper restore PossibleNullReferenceException
            // ReSharper restore PossibleMultipleEnumeration
        }

        [Fact]
        public void Xmls_with_same_content_should_be_equals()
        {
            TwoWayCompare(true, @"<root>toto</root>", @"<root>toto</root>");
        }

        [Fact]
        public void Xmls_with_different_content_should_be_different()
        {
            TwoWayCompare(false, @"<root>toto</root>", @"<root>titi</root>");
        }

        [Fact]
        public void Xmls_with_same_argument_is_equal()
        {
            TwoWayCompare(true, @"<root name=""toto""></root>", @"<root name=""toto""></root>");
        }

        [Fact]
        public void Xmls_with_different_argument_name_is_not_equal()
        {
            TwoWayCompare(false, @"<root name=""toto""/>", @"<root value=""toto""/>");
        }

        [Fact]
        public void Xmls_with_different_argument_value_is_not_equal()
        {
            TwoWayCompare(false, @"<root name=""toto""/>", @"<root name=""titi""/>");
        }

        [Fact]
        public void Xmls_with_different_argument_are_not_equals()
        {
            TwoWayCompare(false, @"<root/>", @"<root name=""toto""/>");
        }

        [Fact]
        public void Xmls_with_same_elements_are_equals()
        {
            TwoWayCompare(true, @"<root><item/></root>", @"<root><item/></root>");
        }

        [Fact]
        public void Xmls_with_different_elements_are_not_equals()
        {
            TwoWayCompare(false, @"<root></root>", @"<root><item/></root>");
        }

        [Fact]
        public void Xml_with_missing_last_element_should_have_one_error()
        {
            const string xml1 = @"<root><item name=""toto""/><item name=""titi""/></root>";
            const string xml2 = @"<root><item name=""toto""/></root>";
            var result = target.Compare(XDocument.Parse(xml1), XDocument.Parse(xml2));

            Assert.Equal(1, result.Count());
        }

        [Fact]
        public void Xml_with_missing_first_element_should_have_one_error()
        {
            const string xml1 = @"<root><item name=""toto""/><item name=""titi""/><item name=""tata""/></root>";
            const string xml2 = @"<root><item name=""titi""/><item name=""tata""/></root>";
            var result = target.Compare(XDocument.Parse(xml1), XDocument.Parse(xml2)).ToList();

            Assert.Equal(1, result.Count());
            Assert.Equal("item", result.First().Name);
            Assert.Null(result.First().Actual);
            Assert.Equal(@"<item name=""toto""></item>", result.First().Expected.ToString());
        }

        [Fact]
        public void Should_return_one_difference_when_missing_last_element()
        {
            TestCompareTwoWayBase(
                @"<root><item name=""toto""/><item name=""titi""/><item name=""tata""/></root>",
                @"<root><item name=""toto""/><item name=""titi""/></root>");
        }

        [Fact]
        public void Should_return_one_difference_when_missing_first_element()
        {
            TestCompareTwoWayBase(
                @"<root><item name=""toto""/><item name=""titi""/><item name=""tata""/></root>",
                @"<root><item name=""titi""/><item name=""tata""/></root>");
        }

        [Fact]
        public void Should_compare_only_first_depth_and_return_only_one_error()
        {
            TestCompareTwoWayBase(
                @"
<root>
    <item name=""toto"">
        <child/>
    </item>
    <item name=""titi""/>
    <item name=""tata""/>
</root>",
                @"
<root>
    <item name=""toto""/>
    <item name=""titi""/>    
    <item name=""tata""/>
</root>", 2);
        }

        [Fact]
        public void Should_return_error_when_missing_duplicates()
        {
            TestCompareTwoWayBase(
                @"
<root>
    <item name=""toto""/>
    <item name=""toto""/>
    <item name=""titi""/>
    <item name=""tata""/>
</root>",
                @"
<root>
    <item name=""toto""/>
    <item name=""titi""/>
    <item name=""tata""/>
</root>");
        }

        [Fact]
        public void Should_be_equals_when_duplicates_are_equals()
        {
            TestCompareTwoWayBase(
                @"
<root>
    <item name=""toto""/>
    <item name=""toto""/>
    <item name=""titi""/>
    <item name=""tata""/>
</root>",
                @"
<root>
    <item name=""toto""/>
    <item name=""toto""/>
    <item name=""titi""/>
    <item name=""tata""/>
</root>", 0);
        }

        [Fact]
        public void Should_not_equal_when_element_name_is_different()
        {
            TestCompareTwoWayBase(
                @"
<root>
    <item name=""toto""/>
    <item name=""toto""/>
    <item name=""titi""/>
    <item name=""tata""/>
</root>",
                @"
<root>
    <item name=""toto""/>
    <item name=""toto""/>
    <test name=""toto""/>
    <item name=""titi""/>
    <item name=""tata""/>
</root>");
        }

        [Fact]
        public void Should_not_equal_when_element_name_is_different_and_missing()
        {
            const string simpleXml = @"
<root>
    <item name=""toto""/>
    <item name=""toto""/>
    <item name=""titi""/>
    <item name=""tata""/>
</root>";
            const string xmlWithMissingAttributeAndAnother = @"
<root>
    <item name=""toto""/>
    <actual name=""toto""/>
    <item name=""titi""/>
    <item name=""tata""/>
</root>";

            var second =
                target.Compare(XDocument.Parse(simpleXml), XDocument.Parse(xmlWithMissingAttributeAndAnother)).ToList();
            Assert.Equal(2, second.Count());
            Assert.True(second.Any(x => x.Actual != null));
            Assert.True(second.Any(x => x.Expected != null));

            var first = target.Compare(XDocument.Parse(xmlWithMissingAttributeAndAnother), XDocument.Parse(simpleXml));
            Assert.Equal(2, first.Count());
            Assert.True(second.Any(x => x.Actual != null));
            Assert.True(second.Any(x => x.Expected != null));
        }

        private void TestCompareTwoWayBase(string xml1, string xml2, int difference = 1)
        {
            TestCompareBase(xml1, xml2, difference);
            TestCompareBase(xml2, xml1, difference);
        }

        private void TestCompareBase(string xml1, string xml2, int differenceCount)
        {
            var expected = XDocument.Parse(xml1);
            var actual = XDocument.Parse(xml2);

            var difference = target.Compare(expected, actual);

            Assert.Equal(differenceCount, difference.Count());
        }

        private void TwoWayCompare(bool expected, string simpleXml, string xmlWithAttribute)
        {
            Assert.Equal(!expected, Compare(simpleXml, xmlWithAttribute).Any());
            Assert.Equal(!expected, Compare(xmlWithAttribute, simpleXml).Any());
        }

        // ReSharper restore UnusedMember.Global
        // ReSharper restore InconsistentNaming
    }
}